﻿using System;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Reflection.Emit;
// Author: Miroslav Matula m.matula@alutam.cz 
// Date : 9.9.2010
namespace Slma.OData.Factory
{
  /// <summary>
  /// Entity_gen je dynamicky generovaná třída. Cílem je vytvořit novou třídu, která bude mít stejné property jako Entita z proxy v Service Reference (třída generované Visual Studiem) 
  /// a bude implementovat následující interfaces:
  /// IDisposable, INotifyPropertyChanged, IEditableObject, IRevertibleChangeTracking, IChangeTracking. Tyto interfaces jsou důležité pro napojení na visuální kontroly v Silvrlight.
  /// Genrování provádí třída ClassFactory. Entity_gen dědí ze třídy Entity. 
  /// Generování třídy obsahuje následující operace: vytvořit nový typ, vytvořit property podle entity z proxy třídy a tyto property namapovat 
  /// na metody SetValue(int propertyNr, object value) a GetValue(int propertyNr) ve třídě Entity.
  /// </summary>
	public static partial class ClassFactory
	{
		private const string _assemblyName = "DynamicEntity";
		private const string _modulName = "mm";

		private static AssemblyBuilder _assemblyBuilder;
		private static ModuleBuilder _modulBuilder;
		//****************** GetAnonymousType *****************
		/// <summary>
		/// Hledá Typ (děděný z entity) v dynamicky vytvořené assembly pro Entity. 
		/// </summary>
		/// <param name="typeName">Název Entity</param>
		/// <returns>EntityTyp</returns>
    public static Type GetAnonymousType(Type ODataEntityType_)
		{
      Type et = null;
      if(_assemblyBuilder != null) et = _assemblyBuilder.GetType(ODataEntityType_.Name + "_dyn");
      if(et == null) {
        et = CreateDynamicEntityType(ODataEntityType_);
      }
      return et;
		}
		//****************** CreateAnonymousType *****************
		/// <summary>
		/// Vytváří novou třídu (Typ) děděnou ze třídy Entity. Přidává property z definice columnlist.
		/// </summary>
		/// <param name="typeName">Název třídy Entity</param>
		/// <param name="columnList">Definice nových property</param>
		/// <returns></returns>
		internal static Type CreateDynamicEntityType(Type ODataEntityType_ )
		{
      // jméno nového typu
      string typeName = ODataEntityType_.Name+"_dyn";
      // Zjistím property, které budu generovat v nové entity
      var columnList = new EntityPropertyDefList();
      foreach(PropertyInfo item in ODataEntityType_.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)){
        columnList.Add(new EntityPropertyDef { 
          PropertyName = item.Name,
          PropertyType = item.PropertyType,
          MaxLength = -1,
          AllowDBNull = false,
          Validate = false
        });  
      }
      //-- generuji typ nové entity ------------- 
			int cntColumn = columnList.Count;
			//ConstructorInfo columnAttributeCtorInfo = typeof(EntityPropertyAttribute).GetConstructor(new Type[] { typeof(string), typeof(int), typeof(bool), typeof(int)});
			ConstructorInfo stringLengthAttributeCtorInfo = typeof(StringLengthAttribute).GetConstructor(new Type[] { typeof(int) }); //StringLengthAttribute(int maximumLength)
			ConstructorInfo displayAttributeCtorInfo = typeof(DisplayAttribute).GetConstructor(new Type[] { });
			PropertyInfo piAutoGenerateField = typeof(DisplayAttribute).GetProperty("AutoGenerateField");

			if (_assemblyBuilder == null)
			{
				// vytváří dynamickou assembly
				_assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(_assemblyName), AssemblyBuilderAccess.Run);//AndSave
				_modulBuilder = _assemblyBuilder.DefineDynamicModule(_modulName, true); //_modulName + ".dll",
			}
			//-- TypeBuilder, dědím z entity ------
      Type baseType = typeof(Entity<>).MakeGenericType(ODataEntityType_);
      TypeBuilder typeBuilder = _modulBuilder.DefineType(typeName, TypeAttributes.Public);
      typeBuilder.SetParent(baseType);

      ILGenerator iLGenerator;

			//-- Property ----------------------------------------
			//-- Na tyto dvě metody budu mapovat nové property.Přes ně získávám a ukládám value 
      MethodInfo methGetData = baseType.GetMethod("GetValue", new Type[] { typeof(int) });
      MethodInfo methSetData = baseType.GetMethod("SetValue", new Type[] { typeof(int), typeof(object) });

      //-- čtu class, kde jsou attributy pro validaci -----------
      Type MetadataType = ODataEntityType_.GetNestedType("Metadata", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
      //-- pro každý sloupec generuj property -------------
			int columnOrder = 0;
			foreach (EntityPropertyDef column in columnList)
			{
				string propName = column.PropertyName;
				Type propType = column.PropertyType;
				//TODO:ClassFactory-DBNull
				bool propAllowDBNull = column.AllowDBNull;

				if (propAllowDBNull && propType.IsValueType)
				{
					// vytváří nullable type
					propType = typeof(Nullable<>).MakeGenericType(new Type[] { propType });
				}
				//-- Define property
				PropertyBuilder propBuilder = typeBuilder.DefineProperty(propName, PropertyAttributes.HasDefault, propType, null);
				//-- Attribute for property ----------
        if(MetadataType != null) {
          PropertyInfo mpInfo = MetadataType.GetProperty(propName);
          if(mpInfo != null) {
            bool validate;
            CopyAttribute(mpInfo, propBuilder, out validate);
            column.Validate = validate; // Tato Property má nastavený ValidationAttribute, propo ji chci testovat
          }
        }
				//-- Get --
				MethodAttributes attributes = MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public;
				MethodBuilder methBuilder_get = typeBuilder.DefineMethod("get_" + propName, attributes, propType, Type.EmptyTypes);
				iLGenerator = methBuilder_get.GetILGenerator();
				iLGenerator.Emit(OpCodes.Ldarg_0);
				iLGenerator.Emit(OpCodes.Ldc_I4, columnOrder);
				iLGenerator.Emit(OpCodes.Call, methGetData);
				if (propType.IsValueType)
				{
					iLGenerator.Emit(OpCodes.Unbox_Any, propType);
				}
				else
				{
					iLGenerator.Emit(OpCodes.Castclass, propType);
				}
				iLGenerator.Emit(OpCodes.Ret);
				propBuilder.SetGetMethod(methBuilder_get);

				//-- set --
				MethodBuilder methBuilder_set = typeBuilder.DefineMethod("set_" + propName, attributes, null, new Type[] { propType });
				iLGenerator = methBuilder_set.GetILGenerator();
				iLGenerator.Emit(OpCodes.Ldarg_0);
				iLGenerator.Emit(OpCodes.Ldc_I4, columnOrder);
				iLGenerator.Emit(OpCodes.Ldarg_1);
				if (propType.IsValueType)
				{
					iLGenerator.Emit(OpCodes.Box, propType);
				}
				iLGenerator.Emit(OpCodes.Call, methSetData);
				iLGenerator.Emit(OpCodes.Ret);
				propBuilder.SetSetMethod(methBuilder_set);
				//-- zvýší pořadí cloupce
				columnOrder++;
			}
  
			//--------------------------------
			Type res = typeBuilder.CreateType();
      //-- nastavím static hodnoty
      res.InvokeMember("InitStaticFields", 
        BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.InvokeMethod,
        System.Type.DefaultBinder, null,
        new object[] { columnList });


			return res;
		}
		//****************** CreateAnonymousType *****************
		// toto v Silverlight nefunguje
		//public static void SaveAssenbly(string fileName_)
		//{
		//  if (_assemblyBuilder != null)
		//  {
		//    _assemblyBuilder.Save(fileName_);
		//    _assemblyBuilder = null; // musí se vytvořit nový
		//  }
		//}
	}
}
